#include "RenderManager.h"
#include "Base.h"
#include "Effects\EffectManager.h"
#include <d3dcompiler.h>
#include "Matrix\Matrix44.h"
#include "Cameras\Camera.h"


#if defined (_DEBUG)
#include "Memory/MemLeaks.h"
#endif

//-------------------------------------------------
CRenderManager::CRenderManager()
	: m_pDevice(NULL)
	, m_pDeviceContext(NULL)
	, m_pSwapChain(NULL)
	, m_pRenderTargetView(NULL)
	, m_pDepthStencil(NULL)
	, m_pDepthStencilView(NULL)
	, m_pDepthEnableState(NULL)
	, m_pDepthDisbaleState(NULL)
	, m_pRasterizerState(NULL)
	, m_pAlphaBlendingEnableState(NULL)
	, m_pAlphaBlendingDisableState(NULL)
	, m_pEffectManager(NULL)
{
	
}

//-------------------------------------------------
CRenderManager::~CRenderManager()
{
	Uninitialize();
}

//-------------------------------------------------
bool CRenderManager::Initialize(HWND _hWnd)
{
	HRESULT hResult;

	// Get the screen size
	RECT rc;
	GetClientRect(_hWnd, &rc);
	m_ScreenSize.x = rc.right - rc.left;
	m_ScreenSize.y = rc.bottom - rc.top;

	// Create the flags
	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	// Create a struct to hold information about the swap chain
	DXGI_SWAP_CHAIN_DESC swapChainDesc;

	// Clear out the sctruct for use
	ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

	// Fill the shwap chain description struct
	swapChainDesc.BufferCount = 1;										//one back buffer
	swapChainDesc.BufferDesc.Width = m_ScreenSize.x;					//set the back buffer width
	swapChainDesc.BufferDesc.Height = m_ScreenSize.y;					//set the back buffer height
	swapChainDesc.BufferDesc.Format =  DXGI_FORMAT_R8G8B8A8_UNORM;		//use 32-bit color
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;		//how shawp chain is to be used
	swapChainDesc.OutputWindow = _hWnd;									//the window to be used
	swapChainDesc.SampleDesc.Count = 1;									//how many multisamples
	swapChainDesc.Windowed = TRUE;										//windowed/fullscreen mode
	swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;		//allow fullscreen switching

	// Create a device, device context and swap chain using te information in the scd struct
	hResult = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, NULL, NULL,
		D3D11_SDK_VERSION, &swapChainDesc, &m_pSwapChain, &m_pDevice, NULL, &m_pDeviceContext);

	if(FAILED(hResult))
		return false;

	// Create a render target view
	ID3D11Texture2D* l_pBackBuffer = NULL;
	hResult = m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&l_pBackBuffer);

	if(FAILED(hResult))
		return false;

	hResult = m_pDevice->CreateRenderTargetView(l_pBackBuffer, NULL, &m_pRenderTargetView);
	CHECKED_RELEASE(l_pBackBuffer);

	if(FAILED(hResult))
		return false;

	// Create depth stencil texture
	D3D11_TEXTURE2D_DESC descDepth;
	ZeroMemory( &descDepth, sizeof(descDepth) );
	descDepth.Width = m_ScreenSize.x;
	descDepth.Height = m_ScreenSize.y;
	descDepth.MipLevels = 1;
	descDepth.ArraySize = 1;
	descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	descDepth.SampleDesc.Count = 1;
	descDepth.SampleDesc.Quality = 0;
	descDepth.Usage = D3D11_USAGE_DEFAULT;
	descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	descDepth.CPUAccessFlags = 0;
	descDepth.MiscFlags = 0;
	hResult = m_pDevice->CreateTexture2D( &descDepth, NULL, &m_pDepthStencil );
	if( FAILED( hResult ) )
		return false;
	
	// Create the depth stencil view
	D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
	ZeroMemory( &descDSV, sizeof(descDSV) );
	descDSV.Format = descDepth.Format;
	descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	descDSV.Texture2D.MipSlice = 0;
	hResult = m_pDevice->CreateDepthStencilView( m_pDepthStencil, &descDSV, &m_pDepthStencilView );
	if( FAILED( hResult ) )
		return false;
	
	m_pDeviceContext->OMSetRenderTargets( 1, &m_pRenderTargetView, m_pDepthStencilView );

	// Create the depth stencil state with depth enabled
	D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
	ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
	depthStencilDesc.DepthEnable = true;
	depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
	depthStencilDesc.StencilEnable = true;
	depthStencilDesc.StencilReadMask = 0xFF;
	depthStencilDesc.StencilWriteMask = 0xFF;

	// Stencil operations if pixel is front-facing.
	depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	// Stencil operations if pixel is back-facing.
	depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	hResult = m_pDevice->CreateDepthStencilState(&depthStencilDesc, &m_pDepthEnableState);
	if(FAILED(hResult))
		return false;

	// Create the depth stencil state with depth disabled
	ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
	depthStencilDesc.DepthEnable = false;
	depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
	depthStencilDesc.StencilEnable = true;
	depthStencilDesc.StencilReadMask = 0xFF;
	depthStencilDesc.StencilWriteMask = 0xFF;
	depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
	depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	hResult = m_pDevice->CreateDepthStencilState(&depthStencilDesc, &m_pDepthDisbaleState);
	if(FAILED(hResult))
		return false;

	// Set the depth stencil state
	m_pDeviceContext->OMSetDepthStencilState(m_pDepthEnableState, 1);

	// Create the rasterizer
	D3D11_RASTERIZER_DESC rasterizerDesc;
	ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));
	rasterizerDesc.AntialiasedLineEnable = false;
	rasterizerDesc.CullMode = D3D11_CULL_BACK;
	rasterizerDesc.DepthBias = 0;
	rasterizerDesc.DepthBiasClamp = 0.0f;
	rasterizerDesc.DepthClipEnable = true;
	rasterizerDesc.FillMode = D3D11_FILL_SOLID;
	rasterizerDesc.FrontCounterClockwise = false;
	rasterizerDesc.MultisampleEnable = false;
	rasterizerDesc.ScissorEnable = false;
	rasterizerDesc.SlopeScaledDepthBias = 0.0f;

	hResult = m_pDevice->CreateRasterizerState(&rasterizerDesc, &m_pRasterizerState);
	if(FAILED(hResult))
		return false;

	// Set the rasterizer
	m_pDeviceContext->RSSetState(m_pRasterizerState);

	// Set de viewport
	D3D11_VIEWPORT viewport;
	viewport.Width = (FLOAT)m_ScreenSize.x;
	viewport.Height = (FLOAT)m_ScreenSize.y;
	viewport.MinDepth = 0.f;
	viewport.MaxDepth = 1.f;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;
	m_pDeviceContext->RSSetViewports(1, &viewport);

	// Create the alpha blending with alpha blend enabled
	D3D11_BLEND_DESC blendDesc;
	ZeroMemory(&blendDesc, sizeof(blendDesc));
	blendDesc.RenderTarget[0].BlendEnable = TRUE;
	blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
	blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
	blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
	blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
	blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
	blendDesc.RenderTarget[0].RenderTargetWriteMask = 0x0f;

	hResult = m_pDevice->CreateBlendState(&blendDesc, &m_pAlphaBlendingEnableState);
	if(FAILED(hResult))
		return false;

	// Create the alpha blending with alpha blend disabled
	blendDesc.RenderTarget[0].BlendEnable = FALSE;

	hResult = m_pDevice->CreateBlendState(&blendDesc, &m_pAlphaBlendingDisableState);
	if(FAILED(hResult))
		return false;

	// Sets the effect manager and loads all the effects and techniques
	m_pEffectManager = new CEffectManager();
	m_pEffectManager->Load("./data/xml/effects.xml");

	return true;
}

//-------------------------------------------------
void CRenderManager::Uninitialize()
{
	if(m_pSwapChain != NULL)
		m_pSwapChain->SetFullscreenState(FALSE, NULL);

	CHECKED_RELEASE(m_pDepthStencil);
	CHECKED_RELEASE(m_pDepthStencilView);

	CHECKED_RELEASE(m_pDepthEnableState);
	CHECKED_RELEASE(m_pDepthDisbaleState);
	CHECKED_RELEASE(m_pRasterizerState);
	CHECKED_RELEASE(m_pAlphaBlendingEnableState);
	CHECKED_RELEASE(m_pAlphaBlendingDisableState);

	CHECKED_RELEASE(m_pSwapChain);
	CHECKED_RELEASE(m_pDevice);
	CHECKED_RELEASE(m_pDeviceContext);
	CHECKED_RELEASE(m_pRenderTargetView);

	CHECKED_DELETE(m_pEffectManager);
}

//-------------------------------------------------
void CRenderManager::ClearTarget()
{
	//Clear the back buffer
	float ClearColor[4] = {0.0f, 0.125f, 0.6f, 1.0f }; // RGBA
	m_pDeviceContext->ClearRenderTargetView(m_pRenderTargetView, ClearColor);

	//Clear the depth stencil
	m_pDeviceContext->ClearDepthStencilView( m_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.f, 0 );
}

//-------------------------------------------------
void CRenderManager::EndRendering()
{
	m_pSwapChain->Present(0, 0);
}

//-------------------------------------------------
void CRenderManager::SetupMatrices(CCamera* _pCamera /* = NULL */)
{
	Mat44f l_View		= m44fIDENTITY;
	Mat44f l_Projection	= m44fIDENTITY;
	Mat44f l_Ortho		= m44fIDENTITY;

	if(_pCamera)
	{
		_pCamera->UpdateMatrices();

		l_View			= _pCamera->GetViewMatrix();
		l_Projection	= _pCamera->GetProjMatrix();
	}
	else
	{
		//Create the view matrix
		XMVECTOR Eye = XMVectorSet( -3.0f, 3.0f, -5.0f, 0.0f );
		XMVECTOR At = XMVectorSet( 0.0f, 0.0f, 1.0f, 0.0f );
		XMVECTOR Up = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );
		XMMATRIX view = XMMatrixLookAtLH( Eye, At, Up );

		//Create the projection matrix
		XMMATRIX projection = XMMatrixPerspectiveFovLH(XM_PIDIV2, m_ScreenSize.x / (FLOAT)m_ScreenSize.y, 0.01f, 100.0f);

		l_View			= Mat44f(view);
		l_Projection	= Mat44f(projection);
	}

	// Create the ortho matrix for 2D rendering
	XMMATRIX ortho	= XMMatrixOrthographicLH((float)m_ScreenSize.x, (float)m_ScreenSize.y, 0.01f, 100.f);
	l_Ortho			= Mat44f(ortho);

	m_pEffectManager->SetCamera(l_View, l_Projection);
	m_pEffectManager->SetOrthoMatrix(l_Ortho);
}

//-------------------------------------------------
void CRenderManager::SetTransform(const Mat44f& _mat)
{
	m_pEffectManager->SetWorldMatrix(_mat);
}